package Schiffeversenken;

import java.util.Random;
import java.lang.Thread;
import java.util.Vector;
import javax.microedition.media.*;

/**
 *
 */
public class Enemy
{
    // members
    private String      _name;
    /**
     * value    true    : AI player
     *          false   : Bluetooth player
     */
    private boolean     _isKI;
    
    private Ship[]      _ships;
    private Field[]     _fields;

    /**
     * key[0]   x-Coord.
     * key[1]   y-Coord.
     * value    -1: water
     *           1: hit
     */
    private int[][]     _shots  = new int[GameInstance.COLS][GameInstance.ROWS];
    private Position    _nextShot;
    private int         _lastShotResult = 0;
    private Position[]  _ai_targetMatrix;
    private Ship        _ai_damagedShip;

    /**
     * value    1   : receive ship-placement information
     *          2   : receive next shot information
     */
    private int                 _threadTask = 0;
    private int                 _threadRate = 500;  // milliseconds between an operationcycle
    private Thread              _thread     = null;
    private GameSynchronisation _threadLoop = null;
    
    // static members
    private static String[] NameList = {
                                            "Bernd", "Fred", "Ingeborg", "Celine", "Mohammed", "Georg", "Selina", "Dr. Braun",
                                            "James T. Kirk", "Obi Wan Kenobi", "Prinzessin Leyla", "Laika", "Iwan", "Mr. Putin",
                                            "Claudia", "Julia", "Romeo", "Manfred von Richthofen", "Sina", "Ina", "Biene Maja",
                                            "Gordon Gürtzig", "Commander Keen", "Lara Croft", "Garret", "Sgt. Patton", "Graf Spree",
                                            "Natasha", "Laura", "Ohura", "Mr. Spock", "Spongebob", "Eric Cartman", "Duckwing Duck"
                                        };


    // constructors

    /**
     * Creates a new Enemy with random name.
     * @param   <boolean>   KI  If true enemy is AI-controlled, otherwise enemy is connected via bluetooth
     */
    public Enemy(boolean KI)
    {
        _isKI = KI;
        _name = Enemy.getRandomName();
        
        while (!Init())
        {}
    }

    /**
     * Creates a new Enemy.
     * @param   <boolean>   KI      If true enemy is AI-controlled, otherwise enemy is connected via bluetooth
     * @param   <String>    name    Specifies a name for the enemy
     */
    public Enemy(boolean KI, String name)
    {
        _isKI = KI;
        _name = name;

        while (!Init())
        {}
    }


    // public methods

    /**
     * Perform a shot at the enemies battleground, at the specified position.
     * @param   <Position>  pos     Defines shooting position
     * @return  <int>               0=water, 1=hit, 2=hit&sunken
     */
    public int receiveShot(Position pos)
    {
        for(int n=0; n<_ships.length; n++)
        {
            if(_ships[n] != null)
            {
                if(_ships[n].isHittingShipAt(pos))
                {
                    _ships[n].Hit();
                    if(_ships[n].isSunken())
                        return 2;
                    else
                        return 1;
                }
            }
        }
        try {
            Player player = Manager.createPlayer(getClass().getResourceAsStream("/sound_waterexpl.wav"), "audio/x-wav");
            player.start();
        } catch (Exception e) {
            e.printStackTrace();
        }

        
        return 0;
    }

    /**
     * This function returns the position of enemies next shot, if this information is already given. 
     * At non-AI opponents, this function should be call periodically for to check the arriving of bluethooth information.
     * @return  <Position>  returns position of enemies next shot or <b><i>null</i></b>, if the enemy didn't fire yet.
     * @todo    * Threadlogic isn't tested yet
     */
    public Position NextShoot ()
    {
        if (_isKI)
        {
            AI_calcNextShot();

            if(_nextShot != null)
            {
                Position retPos = new Position(_nextShot.X(), _nextShot.Y());
                _nextShot = null;
                return retPos;
            }
        }
        else
        {
            // otherwise we check repeatedly the shooting stack for a new shot
            try
            {
                boolean loop = true;
                do
                {
                    if (this.enemyShootingCoords.size() >=
                            this.currentEnemyShootingIndex + 1)
                    {
                        // we have a shot!
                        System.out.println("shot received: " + this.enemyShootingCoords.elementAt(this.currentEnemyShootingIndex));
                        _nextShot = (Position)this.enemyShootingCoords.elementAt(
                                this.currentEnemyShootingIndex++);
                        loop = false;

                    }
                    else
                    {
                        System.out.println("no shot yet ...");
                        // otherwise wait one second then try again
                        // @todo: implement timeout
                        synchronized (this)
                        {
                            try {this.wait(1000);}
                            catch (Exception e) {e.printStackTrace();}
                        }
                    }
                } while (loop);
            }
            catch (Exception e) {}
        }

        if (_nextShot != null)
        {
            Position retPos = new Position(_nextShot.X(), _nextShot.Y());
            _nextShot = null;
            return retPos;
        }
        else
        {
            return null;
        }
    }
    /** All coords the enemy is shooting at (shots enemy has made but not yet
     * displayed to the player) or has made (we do not erase any shot as this
     * may cause thread synchronization problems otherwise).
     */
    private Vector enemyShootingCoords = new Vector(64);
    
    /** Index of last display shot of the enemy (used as iterator) */
    private int currentEnemyShootingIndex = 0;

    /** Add a shot of the enemy */
    public void AppendEnemyShot(Position pos)
    {
        this.enemyShootingCoords.addElement(pos);
    }

    /** Changes given ship states based on given shot coords.
     * Returns  -1  - water hit
     *          0   - hit
     *          1   - hit & sunken
     */
    public int ShootAt (Position aim, Ship[] ships)
    {
        int val = -1, hitValue = 0;

        for(int n=0; n<ships.length; n++)
        {
            if(ships[n] != null)
            {
                if(ships[n].isHittingShipAt(aim))
                {
                    ships[n].Hit();
                    if(ships[n].isSunken())
                    {
                        hitValue = 2;
                        _ai_damagedShip = null;
                    }
                    else
                    {
                        hitValue = 1;
                        _ai_damagedShip = ships[n];
                    }
                    val = 1;
                    break;
                }
            }
        }
        _shots[aim.X()][aim.Y()]    = val;
        _lastShotResult             = val;
        return hitValue;
    }


    // private methods

    /**
     * Initialize importen enemy information, like ship-placement
     * @return  <boolean>   true if information are complete, false if informationcatching thread isn't finished yet.
     * @todo    * NonAI logic isn't tested yet!
     */
    private boolean Init()
    {
        // reset shoots
        for(int x=0; x<GameInstance.COLS; x++)
        {   for(int y=0; y<GameInstance.ROWS; y++)
            {
                _shots[x][y] = 0;
            }
        }

        if (_isKI)
        {
            AI_placeShipsByRandom();
            if ( !(_name != null && _name.length() > 0) )
                _name = getRandomName();

            return true;
        }
        return true;
    }

    /**
     * This function will position the enemy ships randomly at the battleground, 
     * by using the Ship::isValidPosition() - method; any adjustment should be do there!
     */
    private void AI_placeShipsByRandom()
    {
        _ships = new Ship[GameInstance.SHIPAMOUNT];
        for(int n=0; n<GameInstance.SHIPAMOUNT; n++)
        {
            int newShipType = 2;
            switch(n)
            {
                case 1:
                case 2:
                    newShipType = 1;
                    break;
                default:
                    newShipType = 0;
                    break;
            }

            Ship newShip = new Ship(newShipType, 0, 0);

            do
            {
                Random rand = new Random(GameInstance.getRandomSeed());
                newShip.setPosition(rand.nextInt(GameInstance.COLS), rand.nextInt(GameInstance.ROWS));

                if(rand.nextInt(2) == 1)
                    newShip.setOrientation(true);
                else
                    newShip.setOrientation(false);
                
            } while (!newShip.isValidPosition(_ships, n));
            _ships[n] = newShip;
        }
    }

    /**
     * This function will set the NextShot var
     * @return  <boolean>   true next shot was defined, false couldn't find a logical next shot
     * @todo    * it's still unstable!!! Some NullPointer a.s.o.
     */
    private boolean AI_calcNextShot()
    {
        // if there is no damaged ship
        System.out.println("AI calculate next shot...");

        if(_ai_damagedShip != null)
        {
            System.out.println("AI found damaged ship");

            // naehere umgebung abscannen wenn nur ein treffer sonst richtung rausbekommen und checken ob sie bereits begrenzt wurde
            Position[] hitPositions = new Position[_ai_damagedShip.getShipLength()];
            int n = 0;
            for(int x=0; x<GameInstance.COLS; x++)
            {   for(int y=0; y<GameInstance.ROWS; y++)
                {
                    if(_shots[x][y] == 1)
                    {
                        hitPositions[n] = new Position(x, y);
                        n++;
                    }
                }
            }
            System.out.println("AI damaged ship was hit " + _ai_damagedShip.getHits() + " of " + _ai_damagedShip.getShipLength());

            if(_ai_damagedShip.getHits() == 1)
            {   // shot around#
                System.out.println("AI do not know orientation yet try around");

                int[] dirs = {0,1,2,3};    // 0:left, 1:up, 2:right, 3:down
                if(hitPositions[0].X() <= 0 || _shots[hitPositions[0].X()-1][hitPositions[0].Y()] != 0)
                    dirs[0] = -1;
                if(hitPositions[0].Y() <= 0 || _shots[hitPositions[0].X()][hitPositions[0].Y()-1] != 0)
                    dirs[1] = -1;
                if(hitPositions[0].X() >= GameInstance.COLS-1 || _shots[hitPositions[0].X()+1][hitPositions[0].Y()] != 0)
                    dirs[2] = -1;
                if(hitPositions[0].Y() >= GameInstance.ROWS-1 || _shots[hitPositions[0].X()][hitPositions[0].Y()+1] != 0)
                    dirs[3] = -1;

                // just to be sure:
                boolean chk = false;
                for(int i=0; i<4; i++)
                {
                    if (dirs[i] >= 0)
                        chk = true;
                }

                if(chk)
                {
                    int dir = -1;
                    System.out.println("AI try get a random direction");
                    do
                    {
                        Random rand = new Random(GameInstance.getRandomSeed());
                        dir = dirs[rand.nextInt(4)];
                    } while (dir < 0);

                    switch(dir)
                    {
                        case 0:
                            _nextShot = new Position(hitPositions[0].X()-1, hitPositions[0].Y());
                            break;
                        case 1:
                            _nextShot = new Position(hitPositions[0].X(), hitPositions[0].Y()-1);
                            break;
                        case 2:
                            _nextShot = new Position(hitPositions[0].X()+1, hitPositions[0].Y());
                            break;
                        case 3:
                            _nextShot = new Position(hitPositions[0].X(), hitPositions[0].Y()+1);
                            break;
                    }
                    System.out.println("AI direction " + dir + " chosen");

                    return true;
                }
                else
                {
                    _ai_damagedShip = null;
                    System.out.println("AI all surrounding fields are not reachable ... do not know what to do?!");
                    return false;
                }                
            }
            else if(_ai_damagedShip.getHits() > 1)
            {   // try to find orientation
                if(_ai_damagedShip.isHorizontal())
                {
                    System.out.println("AI damaged ship orientates horizontal");
                    int[] possibilitiesX = {-1, -1};

                    // most left point
                    int minX = GameInstance.COLS;
                    for (int x=0; x<hitPositions.length; x++)
                    {
                        if(hitPositions[x] != null && hitPositions[x].X() < minX)
                            minX = hitPositions[x].X();
                    }

                    if(minX>0 && _shots[minX-1][hitPositions[0].Y()] == 0)
                    {
                        possibilitiesX[0] = minX-1;
                    }

                    // most right
                    int maxX = 0;
                    for (int x2=0; x2<hitPositions.length; x2++)
                    {
                        if(hitPositions[x2] != null && hitPositions[x2].X() > maxX)
                            maxX = hitPositions[x2].X();
                    }

                    if(maxX+1<GameInstance.COLS && _shots[maxX+1][hitPositions[0].Y()] == 0)
                    {
                        possibilitiesX[1] = maxX+1;
                    }

                    if(possibilitiesX[0] >= 0 && possibilitiesX[1] >= 0)
                    {
                        Random rand = new Random(GameInstance.getRandomSeed());
                        if(rand.nextInt(2) == 1)
                            _nextShot = new Position(possibilitiesX[0], hitPositions[0].Y());
                        else
                            _nextShot = new Position(possibilitiesX[1], hitPositions[0].Y());

                        return true;
                    }
                    else if(possibilitiesX[0] >= 0)
                    {
                        _nextShot = new Position(possibilitiesX[0], hitPositions[0].Y());
                        return true;
                    }
                    else if(possibilitiesX[1] >= 0)
                    {
                        _nextShot = new Position(possibilitiesX[1], hitPositions[0].Y());
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
                else    // vertical
                {
                    int[] possibilitiesY = {-1, -1};

                    // most upper point
                    int minY = GameInstance.ROWS;
                    for (int y=0; y<hitPositions.length; y++)
                    {
                        if(hitPositions[y] != null && hitPositions[y].Y() < minY)
                            minY = hitPositions[y].Y();
                    }

                    if(minY>0 && _shots[hitPositions[0].X()][minY-1] == 0)
                    {
                        possibilitiesY[0] = minY-1;
                    }

                    // most below
                    int maxY = 0;
                    for (int y2=0; y2<hitPositions.length; y2++)
                    {
                        if(hitPositions[y2] != null && hitPositions[y2].Y() > maxY)
                            maxY = hitPositions[y2].Y();
                    }

                    if(maxY+1<GameInstance.ROWS && _shots[hitPositions[0].X()][maxY+1] == 0)
                    {
                        possibilitiesY[1] = maxY+1;
                    }

                    if(possibilitiesY[0] >= 0 && possibilitiesY[1] >= 0)
                    {
                        Random rand = new Random(GameInstance.getRandomSeed());
                        if(rand.nextInt(2) == 1)
                            _nextShot = new Position(hitPositions[0].X(), possibilitiesY[0]);
                        else
                            _nextShot = new Position(hitPositions[0].X(), possibilitiesY[1]);

                        return true;
                    }
                    else if(possibilitiesY[0] >= 0)
                    {
                        _nextShot = new Position(hitPositions[0].X(), possibilitiesY[0]);
                        return true;
                    }
                    else if(possibilitiesY[1] >= 0)
                    {
                        _nextShot = new Position(hitPositions[0].X(), possibilitiesY[1]);
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
            else
            {
                return false;
            }
        }

        _ai_targetMatrix = new Position[ (int)(Math.ceil( GameInstance.COLS/2 ) * GameInstance.ROWS) ];
        int cnt = 0;
        for (int a=0; a<=Math.floor(GameInstance.COLS/2); a++)
        {   for (int b=0; b<=GameInstance.ROWS; b++)
            {
                Position newPosition = new Position((a*2) + (b%2), b);
                if (        newPosition.X() >= 0 && newPosition.X() < GameInstance.COLS
                        &&  newPosition.Y() >= 0 && newPosition.Y() < GameInstance.ROWS
                        && _shots[newPosition.X()][newPosition.Y()] == 0 )
                {
                    _ai_targetMatrix[cnt] = newPosition;
                    cnt++;
                }
            }            
        }

        Random rand = new Random(GameInstance.getRandomSeed());
        int randIndex = 0;

        do
        {
            randIndex = rand.nextInt(cnt);
        } while (_ai_targetMatrix[randIndex] == null);

        if (_ai_targetMatrix[randIndex] != null)
        {
            _nextShot = _ai_targetMatrix[randIndex];
            return true;
        }
        return false;
    }


    // properties

    /**
     * @return <String> Gets opponent name
     */
    public String getName()
    {
        return _name;
    }
    
    /**
     * @param <String>  name    Sets opponent name
     */
    public void setName(String name)
    {
        _name = name;
    }

    /**
     * @return <boolean> Returns if enemy is a KI, if false its an other player connected by bluetooth
     */
    public boolean getType()
    {
        return _isKI;
    }

    /**
     * @param <boolean> isKI    change the enemy-type, true for KI, false for bluetooth-player
     */
    public void setType(boolean isKI)
    {
        _isKI = isKI;
    }

    /**
     * @return  <Ship[]>    an Array with all enemy ships
     */
    public Ship[] getShips()
    {
        return _ships;
    }

    /**
     * @return  <int[][]>   Get enemy shots
     */
    public int[][] getShots()
    {
        return _shots;
    }

    /**
     * @return  <int>   result of last shot (0: no result, -1: water, 1: hit)
     */
    public int getLastShotResult()
    {
        return _lastShotResult;
    }


    // private classes
    /**
     * This runnable thread is used for network operations.
     * @todo    * not tested yet
     *          * needs the bluetooth implementation
     */
    private class GameSynchronisation implements Runnable
    {
        public void run()
        {
            boolean done    = false;

            do
            {
                switch (_threadTask)
                {
                    case 1: // +++ place enemy ships +++
                        // format: x0,y0,horizontal0;x1,y1,horizontal1;x2,y2,horizontal2;x3... etc.
                        String resp = "1,2,0;3,4,1;5,5,1;1,1,1;"; /* String resp = Bluetooth.getResponseStream; */
                        if (resp != null && resp.length() > 0)
                        {
                            String[] respShips  = GameInstance.str_split(resp, ";");
                            if (respShips.length == GameInstance.SHIPAMOUNT)
                            {
                                boolean chk     = true;
                                Ship[] ships    = new Ship[GameInstance.SHIPAMOUNT];
                                for (int n=0; n < GameInstance.SHIPAMOUNT; n++)
                                {
                                    int shipType = 2;
                                    switch(n)
                                    {
                                        case 1:
                                        case 2:
                                            shipType = 1;
                                            break;
                                        default:
                                            shipType = 0;
                                            break;
                                    }

                                    String[] shipInfo   = GameInstance.str_split(respShips[n], ",");
                                    if (shipInfo.length == 3)
                                    {
                                        Ship newShip = new Ship(shipType, Integer.parseInt(shipInfo[0]), Integer.parseInt(shipInfo[1]));
                                        if(Integer.parseInt(shipInfo[2]) == 1)
                                            newShip.setOrientation(true);
                                        else
                                            newShip.setOrientation(false);

                                        ships[n]    = newShip;
                                    }
                                    else
                                    {
                                        chk = false;
                                    }
                                }

                                if (chk)
                                {
                                    _ships = ships;
                                    done = true;
                                }
                            }
                        }
                        break;


                    case 2: // +++ Peforming a shot +++
                        if ( true /* gotPositionByBluetooth != null */ )
                        {
                            _nextShot = new Position(2, 3); /* gotPositionByBluetooth */;
                            done = true;
                        }
                        break;


                    default:
                        done = true;
                        break;
                }

                if(!done)
                {
                    try
                    {
                        _thread.sleep(_threadRate);
                    } catch (InterruptedException ex) {}
                }
                
            } while (!done);
            
            _thread     = null;
            _threadTask = 0;
            _threadLoop = null;
        }        
    }


    // static functions

    /**
     * Returns a random name from name array.
     * @return <String> name
     */
    public static String getRandomName()
    {
        Random rand = new Random(GameInstance.getRandomSeed());
        return Enemy.NameList[rand.nextInt(Enemy.NameList.length)];
    }
}
